local BattleModel = class("BattleModel",function() return display.newNode() end)

local Connecter = require("app.utils.TCPConnecter")

local Packet = require("app.utils.Packet")
local BattleLoop = import(".BattleLoop")
local EntityModel = import(".EntityModel")

local BattleLoop_tick_ = BattleLoop.tick_
local BattleLoop_getCurrentState = BattleLoop.getCurrentState
local BattleLoop_getBattleLogicTime = BattleLoop.getBattleLogicTime
local BattleLoop_getAccumulator = BattleLoop.getAccumulator
local BattleLoop_processEvents = BattleLoop.processEvents
local BattleLoop_syncState = BattleLoop.syncState
local BattleLoop_getIsRewind = BattleLoop.getIsRewind
local BattleLoop_IsSingleMode = BattleLoop.isSingleMode


local State_getEntityByIdx = battle.State.getEntityByIdx
local State_getEntityCount = battle.State.getEntityCount
local State_getLeftEscapeTime = battle.State.getLeftEscapeTime
local State_isGameStarted = battle.State.isGameStarted
local State_getTargetProgress = battle.State.getTargetProgress


BattleModel.Event_Start = "Battle_Start"
BattleModel.Event_GameOver = "Event_GameOver"
BattleModel.Event_Set_All_UI_Visible = "Event_Set_All_UI_Visible"
BattleModel.Event_Send_Input = "Event_Send_Input"
BattleModel.Event_ShowErrorMessage = "Event_ShowErrorMessage"
BattleModel.Event_Sync_Entity_State = "Event_Sync_Entity_State"
BattleModel.Event_Sync_UI_State = "Event_Sync_UI_State"
BattleModel.Event_Fetch_Self_Skills = "Event_Fetch_Self_Skills"
BattleModel.Event_Sync_Entity_Pos = "Event_Sync_Entity_Pos"
BattleModel.Event_On_Damage = "Event_On_Damage"
BattleModel.Event_UNIT_TALK = "Event_UNIT_TALK"
BattleModel.Event_PROJECTILE_BOUNCE = "Event_PROJECTILE_BOUNCE"
BattleModel.Event_Show_BuffDesc = "Event_Show_BuffDesc"
BattleModel.Event_Show_Blind = "Event_Show_Blind"
BattleModel.Event_FirstSend_AutoBattle = "Event_FirstSend_AutoBattle"
BattleModel.Event_FirstSend_KeepBasicAttack = "Event_FirstSend_KeepBasicAttack"
BattleModel.Event_Check_BattleState = "Event_Check_BattleState"
BattleModel.Event_SET_NEXT_UPDATE_TIME = "Event_SET_NEXT_UPDATE_TIME"
BattleModel.Event_UNIT_TEAM_MARK = "Event_UNIT_TEAM_MARK"
BattleModel.Event_UNIT_IMMUNE_BUFF = "Event_UNIT_IMMUNE_BUFF"
BattleModel.Event_UNIT_REVIVE = "Event_UNIT_REVIVE"
BattleModel.Event_SOUL_CHANGED = "Event_SOUL_CHANGED"
BattleModel.Event_BATTLE_START = "Event_BATTLE_START"
BattleModel.Event_LION_BOSS_APPEAR = "Event_LION_BOSS_APPEAR"
BattleModel.Event_SHOW_TIPS = "Event_SHOW_TIPS"
BattleModel.Event_LION_REFRESH_KILLLIST = "Event_LION_REFRESH_KILLLIST"
BattleModel.Event_BATTLE_ROUND_PREPARE = "Event_BATTLE_ROUND_PREPARE"
BattleModel.Event_BATTLE_ROUND_SPACE = "Event_BATTLE_ROUND_SPACE"
BattleModel.Event_Enter_Battle = "Event_Enter_Battle"
BattleModel.SINGLE_GAME_BEGIN = "Event_Single_Game_Begin"
BattleModel.Event_DBM_SKILL_HIT = "Event_DBM_SKILL_HIT"
BattleModel.Event_DBM_ON_HP = "Event_DBM_ON_HP"
BattleModel.Event_CLIENT_BATTLE_OVER = "Event_CLIENT_BATTLE_OVER"

local Entity_getId = battle.Entity.getId

local DebugMode = false
local function debugLog(...)
    if DebugMode then
        print("[battle]",...)
    end
end

function BattleModel.createWithUrl(host,port,params)
    local model = BattleModel.new(host,port,params)
    return model
end

function BattleModel:onSingleModeGame()
    local msg, rawData = BattleManager:getSingleModeStartData()
    self:S2C_Start(msg, rawData)
end

function BattleModel:ctor(host,port,params)
    self.isConnected = false
    self.isStartEnd = false
    self.waitProcessServerCommand = {}
    self.waitProcessServerState = {}
    self.outgoingCommandSeq = 0
    self.outgoingCommands = {}
    self.currentCommandSeqs = {}

    self.lastCommandTime = 0
    self.battleEnterParam = {}
    self.isServerEnterBattle = false

    cc(self):addComponent("components.behavior.SimpleEvent"):exportMethods()

    self:enableNodeEvents()

    self._connectParams = params
    self._host = host
    self._port = port

    if BattleManager:isSingleMode() then
        -- 单机战斗
        -- 支持2种数据加载模式：1.读取本地文件；2.来自游戏服的协议数据
        -- 支持2种验证模式：1.需要验证；2.不需要验证
        self:addEventListener(self.SINGLE_GAME_BEGIN, handler(self,BattleModel.onSingleModeGame))
        return
    end

    if params then
        local connector = Connecter.new()
        connector.tag = "Battle"
        connector.keepalive = true
        connector:on("battle2.S2C_Start",handler(self,BattleModel.S2C_Start))
        connector:on("battle2.S2C_InputCommand",handler(self,BattleModel.S2C_InputCommand))
        connector:on("battle2.S2C_ErrorMessage",handler(self,BattleModel.S2C_ErrorMessage))
        connector:on("battle2.S2C_SwitchAutoBattle",handler(self,BattleModel.S2C_SwitchAutoBattle))
        --connector:on("battle2.S2C_GameOver",handler(self,BattleModel.S2C_GameOver)
        connector:on("battle2.S2C_SyncState",handler(self,BattleModel.S2C_SyncState))
        connector:on("battle2.S2C_Ready",handler(self,BattleModel.S2C_Ready))
        connector:on("battle2.S2C_UpdateReadyState",handler(self,BattleModel.S2C_UpdateReadyState))
        connector:on("battle2.S2C_Enter",handler(self,BattleModel.S2C_Enter))
        connector:on(Connecter.EVENT_CLOSE,handler(self,BattleModel.onConnectNotHealth_))
        connector.pingInterval = 2
        connector:setPingFunc(function(connecter,cb)
            connecter:send("battle2.Ping",{},function()
                if cb then
                    cb()
                end
            end)
        end)
        self.connector = connector
        self:connect()
    end
end

function BattleModel:onEnter()
    debugLog("--------BattleModel:onEnter----------")
end

function BattleModel:connect()
    release_print("[Battle] connect start")
    local params = self._connectParams
    local cb = function(backParam1, backParam2)
        local isnull = tolua.isnull(self)
        if not isnull then
            local isError = backParam1 ~= 0
            local errorMsg = backParam2
            release_print("[Battle] connect end", backParam1, errorMsg)
            if isError then
                if self.connector then
                    self:onConnectNotHealth_({forceClosing = false, error=errorMsg})
                end
            else
                self:removeReconnectDialog()
                if self.connector then
                    self._connectParams.battlePath = nil
                    self.connector:send("battle2.C2S_Start",self._connectParams)
                end
            end
        end
    end
    if params and self.connector then
        self.connector:open(self._host,self._port,cb,3,700)
    end
end

function BattleModel:onConnectNotHealth_(event)
    local forceClosing = event.forceClosing

    if not forceClosing then
        -- EM:notify("onBattleLostConnection")
        self:showReconnectDialog()
        -- UIHandler:displayConnecterStateUI(false)
    end
end

-- 重连策略：在游戏服链接的情况下，弹框选择重连或者重启，游戏服链接失效时，不做额外处理
function BattleModel:showReconnectDialog()
    if not app.session:isOpen() then return end

    self:removeReconnectDialog()

    if self._clientServerNotMatchMsg then
        local msg = self._clientServerNotMatchMsg
        self._clientServerNotMatchMsg = nil
        local confirmDialog = display.newMessageBox(msg,function()
            if not cc.isDead(self) then
                self._reconnectDialog=nil
            end
            app:relaunchApp()
        end)
        confirmDialog:setConfirmText(L("tips_1000004"))
        UIHandler:addPopDialog(confirmDialog, 1, true)
        self._reconnectDialog = confirmDialog
    elseif self._battleNotExistMsg then
        local msg = self._battleNotExistMsg
        self._battleNotExistMsg = nil
        local confirmDialog = display.newMessageBox(msg,function()
            if not cc.isDead(self) then
                self._reconnectDialog=nil
            end
            -- 清理战斗状态
            if BattleManager:isBattle() then
                BattleManager:goOutBattle(nil,nil,true)
            end
        end)
        confirmDialog:setConfirmText(L("tips_ok"))
        UIHandler:addPopDialog(confirmDialog, 1, true)
        self._reconnectDialog = confirmDialog
    else
        local reconnectDialog = self._reconnectDialog
        if cc.isDead(reconnectDialog) then
            reconnectDialog = require("app.widget.ReconnectDialog").new(
                L("tips_1000003"),
                function()
                    if cc.isAlive(self) and BattleManager:isBattle() then
                        local enterBattleInfo = BattleManager:getEnterBattleInfo()
                        BattleManager:goOutBattle(nil,nil,true)
                        BattleManager:setEnterBattleInfo(enterBattleInfo)
                        BattleManager:prepareBattle(true)
                    end
                end,
                function()
                    self._reconnectDialog=nil
                    app:relaunchApp()
                end)

            reconnectDialog:setConfirmText(L("tips_1000005"))
            reconnectDialog:setCancelText(L("tips_1000004"))
            UIHandler:addPopDialog(reconnectDialog, 1, true)
            self._reconnectDialog = reconnectDialog
        end
        reconnectDialog:refreshConfirmBtn()
    end
end

function BattleModel:removeReconnectDialog()
    local reconnectDialog = self._reconnectDialog
    if not cc.isDead(reconnectDialog) then
        reconnectDialog:removeFromParent()
    end
    self._reconnectDialog = nil
end

function BattleModel:onBattleFinished(event)
    local commands = event.commands
    local battleResult = event.result
    if BattleManager:isSingleMode() then
        if event.finishedReason == Enums.FinishedReason.FR_Timeout then
            BattleManager:setEscapeInSingleMode(true)
            app.session:request("battle2.C2S_SingleBattleEscape",
                {
                    battleID = self._connectParams.battleID,
                },
                function(ret)
                    if ret.ok then
                        display.pushToast(L("lua_code_text_276"))
                    end
                end)
        end
        if BattleManager:isSingleModeNeedVerify() and not BattleManager:isEscapeInSingleMode() then
            BattleManager:clearSingleModeStartData()
            local resultData =
            {
                frameCount = battleResult:frameCount(),
                wonTeam = battleResult:wonTeamId(),
                kills = {},
            }
--            for _, killedUnit in ipairs(battleResult:getKilledUnits()) do
--                table.insert(resultData.kills,
--                    {
--                        kind = protobuf.enum_name("battle2.EUnitKind", killedUnit:unitKind()),
--                        teamId = killedUnit:teamId(),
--                        bnpcId = killedUnit:bnpcId(),
--                    })
--            end
            local commandsData = {}
            for i, command in ipairs(commands) do
                commandsData[i] =
                {
                    actualTime = command:getActualTime(),
                    cmdId = protobuf.enum_name("battle2.CmdID", command:getCmdID()),
                    unitId = command:getUnitId(),
                    skillId = command:getSkillId(),
                    subSkillId = command:getSubSkillId(),
                    targetType = protobuf.enum_name("battle2.TargetType", command:getTargetType()),
                    targetValue = command:getTargetValue(),
                    seq = command:getSeq(),
                    content = command:getContent(),
                    flagId = command:getFlagId(),
                    InstanceID = command:getInstanceID(),
                    BuyItemID = command:getBuyItemID(),
                    BuyNum = command:getBuyItemNum(),
                    CollectResID = command:getCollectID(),
                    interactionType = command:getInteractionType(),
                }
            end
            -- 和游戏服验证单机战斗结果
            app.session:request("battle2.C2S_SingleBattleVerify",
                {
                    battleID = self._connectParams.battleID,
                    btVer = self._connectParams.btVer,
                    cfgVer = self._connectParams.cfgVer,
                    result = resultData,
                    commands = commandsData,
                },
                function(ret)
                    if ret.ok then
--                        display.pushToast(L("单机战斗验证成功"))
                    end
                end)
        else
            self:checkBattleState()
        end
    else
        self:checkBattleState()
    end
end

function BattleModel:OnSingleResultCheck(event)
    local battleId = event.battleId
    local cmdblock = event.cmdblock
    local cmdsize = event.cmdsize

    local connector = self.connector
    if connector then
        connector:send("battle2.C2S_SingleResult", {battleId=battleId, cmdblock=cmdblock, cmdsize= cmdsize,})
    end
end

function BattleModel:onBattleEscapeEndTips(event)
    local escapeTeamId = event.escapeTeamId
    local controlledEntity = self:getControlledEntity()
    if controlledEntity.teamId == escapeTeamId then
        app:sendMsg("IMModel", "addSystemMsg", L("tips_1000108"))
    end
end

function BattleModel:onBattleEscapeTips(event)
    local escapeTeamId = event.escapeTeamId
    local controlledEntity = self:getControlledEntity()
    if controlledEntity.teamId == escapeTeamId then
        app:popMessage("escaping", nil, nil, Enums.ToastTag.battle)
    end
end

function BattleModel:checkBattleState()
    local runningAction = self:getActionByTag(Enums.CommonActionTag.BattleCheck)
    if not runningAction then
        local actions = {}
        table.insert(actions, cc.DelayTime:create(5))
        table.insert(actions, cc.CallFunc:create(function()
            self:dispatchEvent(BattleModel.Event_Check_BattleState)
        end))
        local action = cc.Sequence:create(actions)
        action:setTag(Enums.CommonActionTag.BattleCheck)
        self:runAction(action)
    end
end

function BattleModel:onExit()
    debugLog("--------BattleModel:onExit----------")
    --停止打印日志
    if DEBUG_BATTLE > 0 then
        battle.BattleLogger:getInstance():stop()
    end
    self:removeAllEventListeners()

    local connector = self.connector
    if connector then
        print("==========onExit close connect!!!")
        connector:off(nil)
        connector:close()
        self.connector = nil
    end

    self:removeReconnectDialog()

    --关闭竞技场界面
    if self:isPvpBattle() or self:isMeleeBattle() then
        UIHandler:handleMessage({id = Constant.FUNC_TYPE_ARENA_VS,mode = "close"})
    end
end

function BattleModel:exitBattle()
    local connector = self.connector
    if connector then
        print("==========exitBattle close connect!!!")
        connector:off(nil)
        connector:close()
        self.connector = nil
    end

    self:removeReconnectDialog()
end

--function BattleModel:getTime()
--    local time = app:getUTCTime()
--    return time
--end

function BattleModel:init()
    debugLog("--------BattleModel:init----------")
end

function BattleModel:dump()
    print("Entitys:{")
    local keys = {
        "id", "mine", "name","type","instanceId", "maskId","weaponId", "shield", "pos","dir","stance","bossTag", "size","hp","hpMax","mana"
    }
    for k,v in pairs(self.entities) do
        print("{")
        for _,key in ipairs(keys) do
            print(" "..key.."="..tostring(v[key]))
        end
        -- print(" skills: "..table.concat(v.skills,","))
        print("}")
        print(" ")
    end
    print("}")
end

function BattleModel:getControlledEntityId()
    return self.controlledEntityId
end

function BattleModel:getMyTeamId()
    return self.myTeamId
end

function BattleModel:getEnemyTeamId()
    return self.enemyTeamId
end

function BattleModel:getCfg(cfgId)
    if self.mBattleLoop then
        print("-----------cfgId:"..cfgId)
        return self.mBattleLoop:getUnitConfig(cfgId)
    end
    return nil
end

function BattleModel:getObjectConfig(projectileResId)
    if self.mBattleLoop then
       return self.mBattleLoop:getObjectConfig(projectileResId)
    end
    return nil
end

function BattleModel:createEntityModel(id, protoUnit, myUnitId, isSummonUnit, isRefreshEntity, isKofEntity)
    local isTimeMonster = (protoUnit.npcId ~= 0) and (protoUnit.npcId == self.timeMonsterNpcId)
    local entityModel = EntityModel.create(id, protoUnit, self.myTeamId,
        isTimeMonster, isSummonUnit, isRefreshEntity, isKofEntity)

    local skillCfgs = {}
    for _, protoSkill in pairs(protoUnit.skills) do
        local skillCfg = self.mBattleLoop:getSkillConfig(protoSkill.id)
        table.insert(skillCfgs,
            {
                skillCfg = skillCfg,
                level = protoSkill.level,
                classSkillId = protoSkill.classSkillId,
                comboSkills = protoSkill.comboSkills
            })
    end
    entityModel:setSkills(skillCfgs)

    if myUnitId == entityModel:getId() then
        self.controlledEntityId = entityModel:getId()
        self.controlledEntityModel = entityModel
    end

    return entityModel
end

function BattleModel:start(data, battleCore)
    print("BattleModel:start()")
    local myUnitId = -1
    local cmds = {}

    local units = {}
    local summonUnits = {}
    local refreshUnits = {}
    local kofUnits = {}
    local serverRefreshUnits = {}
    local refreshBattleUnits = {}

    local unitsProtoData = {}
    local summonUnitsProtoData = {}
    local refreshUnitsProtoData = {}
    local kofUnitsProtoData = {}
    local refreshBattleUnitsProtoData = {}
    local serverRefreshUnitsProtoData = {}

    self.battleEnterParam.actualTime = 0
    self.battleEnterParam.mapX = 100000
    self.battleType = Enums.EBattleType.BtType_Normal
    self.roundNums = 1
    self.timeMonsterNpcId = 0

    local battleDesc = data.desc
    self._battleId = battleDesc.formationId
    -- dump(battleDesc,"battleDesc",6)
    if BattleManager:isSingleMode() then
        self.isServerEnterBattle = true
    else
        self.isServerEnterBattle = data.isEnter
        debugLog("=========start data.isEnter:",data.isEnter)
    end

    unitsProtoData = battleDesc.units or {}
    summonUnitsProtoData = battleDesc.summonUnits or {}
    refreshUnitsProtoData = battleDesc.refreshUnits or {}
    serverRefreshUnitsProtoData = battleDesc.ServerrefreshUnits or {}
    kofUnitsProtoData = battleDesc.kofUnits or {}
    local refreshBattle = battleDesc.refreshBattle
    for i = 1, #refreshBattle do
        for j = 1, #(refreshBattle[i].refreshUnits) do
            table.insert(refreshBattleUnitsProtoData, refreshBattle[i].refreshUnits[j])
        end
    end
    -- dump(unitsProtoData,"unitsProtoData",6)
    -- dump(summonUnitsProtoData,"summonUnitsProtoData",6)
    -- dump(refreshUnitsProtoData,"refreshUnitsProtoData",6)
    -- dump(kofUnitsProtoData,"kofUnitsProtoData",6)
    -- dump(ServerrefreshUnitsProtoData,"ServerrefreshUnitsProtoData",6)
    -- dump(battleDesc.nextNpcs, "nextNpcs", 10)
    -- dump(battleDesc.formations, "formations", 10)
    -- dump(battleDesc.refreshUnits, "refreshUnits", 10)
    -- dump(battleDesc.refreshBattle, "refreshBattle", 10)

    cmds = battleDesc.cmds or {} --todo 断网后连接后重新执行cmds

    self.battleEnterParam.actualTime = battleDesc.actualTime or 0
    self.battleEnterParam.mapX = battleDesc.mapX or 100000
    self.battleType = protobuf.enum_id("battle2.EBattleType", battleDesc.btType)
    --kof战斗和赤道大乱斗战斗中要取消自动战斗
    if self:isKofBattle() or self:isMeleeBattle() then
        UD.setting:setAutoBattle(false)
        UD:updateSetting(UD.setting)
    end
    self.roundNums = battleDesc.BattleTimes or 1

    if not self.mBattleLoop then
        debugLog("---------create BattleLoop:create")
        self.mBattleLoop = BattleLoop.create(battleCore, self)
        -- TODO: Loop需要监听的事件为什么在model中处理？
        self.mBattleLoop:addEventListener(BattleLoop.Event_Loop_End,handler(self,BattleModel.onBattleLoopEnd))
        self.mBattleLoop:addEventListener(BattleLoop.Event_Post_Processed,handler(self,BattleModel.postProcessed))
        self.mBattleLoop:addEventListener(BattleLoop.Event_Battle_Verify,handler(self,BattleModel.C2S_Verify))
        self.mBattleLoop:addEventListener(BattleLoop.Event_Battle_Finished,handler(self,BattleModel.onBattleFinished))
        self.mBattleLoop:addEventListener(BattleLoop.Event_Single_Mode_Check,handler(self,BattleModel.OnSingleResultCheck))

        self.mBattleLoop:addEventListener(BattleLoop.Event_Battle_Escape_Tips,handler(self,BattleModel.onBattleEscapeTips))
        self.mBattleLoop:addEventListener(BattleLoop.Event_Battle_Escape_End_Tips,handler(self,BattleModel.onBattleEscapeEndTips))

        if battleDesc.BattleTimes then
            self.timeMonsterNpcId = self.mBattleLoop:getTimeMonsterNpcId(battleDesc.BattleTimes)
        end

        local function onGameOver(event)
            -- print("BattleModel start onGameOver callback")
            self.isGameOver = true

            local winTeamId = event.winTeamId
            local isEscapeBattle = event.isEscapeBattle

            local unit = self.mBattleLoop:getEntityById(self.controlledEntityId)
            local isWin = winTeamId == unit:teamId()
            local ownerDir = self:getControlledEntity():getDir()
            local victoryDir = 1
            if isWin then
                victoryDir = ownerDir
            else
                if ownerDir == 1 then
                    victoryDir = -1
                else
                    victoryDir = 1
                end
            end
            self:dispatchEvent(BattleModel.Event_GameOver,{isWin = isWin, victoryDir = victoryDir, isEscapeBattle = isEscapeBattle})
        end
        self.mBattleLoop:addEventListener(BattleLoop.Event_Game_Over,onGameOver)

        local function setBattleUIVisible(event)
            self:dispatchEvent(BattleModel.Event_Set_All_UI_Visible,{isVisible = event.isVisible})
        end
        self.mBattleLoop:addEventListener(BattleLoop.Event_Set_BattleUI_Visible,setBattleUIVisible)

        local function onDamage( event, isBasicAttack )
            self:dispatchEvent(BattleModel.Event_On_Damage, event, isBasicAttack)
        end
        self.mBattleLoop:addEventListener(BattleLoop.Event_On_Damage,onDamage)

        local function onUnitTalk(time,unitId,text)
            self:dispatchEvent(BattleModel.Event_UNIT_TALK,time,unitId,text)
        end
        self.mBattleLoop:addEventListener(BattleLoop.Event_UNIT_TALK,onUnitTalk)

        local function onProjectileBounce(event)
            self:dispatchEvent(BattleModel.Event_PROJECTILE_BOUNCE,{projectileId = event.projectileId, targetId = event.targetId, casterId = event.casterId, pos = event.pos})
        end
        self.mBattleLoop:addEventListener(BattleLoop.Event_PROJECTILE_BOUNCE,onProjectileBounce)

        local function onUnitTeamMark(event)
            self:dispatchEvent(BattleModel.Event_UNIT_TEAM_MARK,{time = event.time, targetId = event.targetId, casterId = event.casterId, flagId = event.flagId})
        end
        self.mBattleLoop:addEventListener(BattleLoop.Event_UNIT_TEAM_MARK,onUnitTeamMark)

        local function onUnitImmuneBuff(event)
            self:dispatchEvent(BattleModel.Event_UNIT_IMMUNE_BUFF,{time = event.time, casterId = event.casterId, targetId = event.targetId, tag = event.tag})
        end
        self.mBattleLoop:addEventListener(BattleLoop.Event_UNIT_IMMUNE_BUFF,onUnitImmuneBuff)

        local function onUnitRevive(event)
            self:dispatchEvent(BattleModel.Event_UNIT_REVIVE,{time = event.time, targetId = event.targetId, pos = event.pos})
        end
        self.mBattleLoop:addEventListener(BattleLoop.Event_UNIT_REVIVE,onUnitRevive)

        local function onUnitSoulChanged(event)
            self:dispatchEvent(BattleModel.Event_SOUL_CHANGED, event)
        end
        self.mBattleLoop:addEventListener(BattleLoop.Event_SOUL_CHANGED, onUnitSoulChanged)

        local function onBattleStart(event)
            self:dispatchEvent(BattleModel.Event_BATTLE_START,{time = event.time})
        end
        self.mBattleLoop:addEventListener(BattleLoop.Event_BATTLE_START,onBattleStart)

        local function onLionBossAppear(event)
            self:dispatchEvent(BattleModel.Event_LION_BOSS_APPEAR,{time = event.time})
        end
        self.mBattleLoop:addEventListener(BattleLoop.Event_LION_BOSS_APPEAR,onLionBossAppear)

        local function onShowTips(event)
            self:dispatchEvent(BattleModel.Event_SHOW_TIPS,event)
        end
        self.mBattleLoop:addEventListener(BattleLoop.Event_SHOW_TIPS,onShowTips)

        local function onLionRefreshKillList(event)
            self:dispatchEvent(BattleModel.Event_LION_REFRESH_KILLLIST)
        end
        self.mBattleLoop:addEventListener(BattleLoop.Event_LION_REFRESH_KILLLIST,onLionRefreshKillList)

        local function onBattleRoundPrepareStart(event)
            self:dispatchEvent(BattleModel.Event_BATTLE_ROUND_PREPARE,{time = event.time, round = event.round})
        end
        self.mBattleLoop:addEventListener(BattleLoop.Event_BATTLE_ROUND_PREPARE,onBattleRoundPrepareStart)

        local function onBattleRoundSpace(event)
            self:dispatchEvent(BattleModel.Event_BATTLE_ROUND_SPACE)
        end
        self.mBattleLoop:addEventListener(BattleLoop.Event_BATTLE_ROUND_SPACE,onBattleRoundSpace)

        local function onShowBuffDesc(event)
            self:dispatchEvent(BattleModel.Event_Show_BuffDesc,{buffId = event.buffId, casterId = event.casterId, bearerId = event.bearerId, displayDesc = event.displayDesc, displaySort = event.displaySort})
        end
        self.mBattleLoop:addEventListener(BattleLoop.Event_Show_BuffDesc,onShowBuffDesc)

        local function onShowBuffBlind(event)
            self:dispatchEvent(BattleModel.Event_Show_Blind)
        end
        self.mBattleLoop:addEventListener(BattleLoop.Event_Show_Blind,onShowBuffBlind)

        local function onSetNextUpdateTime(time)
            self:dispatchEvent(BattleModel.Event_SET_NEXT_UPDATE_TIME, time)
        end
        self.mBattleLoop:addEventListener(BattleLoop.Event_SET_NEXT_UPDATE_TIME,onSetNextUpdateTime)

        local function onDBMSkillHit(event)
            self:dispatchEvent(BattleModel.Event_DBM_SKILL_HIT, event)
        end
        self.mBattleLoop:addEventListener(BattleLoop.Event_DBM_SKILL_HIT,onDBMSkillHit)

        local function onClientBattleOver(event)
            self:dispatchEvent(BattleModel.Event_CLIENT_BATTLE_OVER)
        end
        self.mBattleLoop:addEventListener(BattleLoop.Event_CLIENT_BATTLE_OVER,onClientBattleOver)

        self:addChild(self.mBattleLoop)
    end

    local function assembleUnitsFromProtoData(unitsProto)
        local unitList = {}
        local unit
        for i = 1, #unitsProto do
            unit =
            {
                protoUnit = unitsProto[i],
                kind = protobuf.enum_id("battle2.EUnitKind", unitsProto[i].kind),
                uid = unitsProto[i].userBaseInfo.uid,
                faceDir = unitsProto[i].faceDir,
                teamId = unitsProto[i].teamId,
            }
            table.insert(unitList, unit)
        end
        return unitList
    end

    units = assembleUnitsFromProtoData(unitsProtoData)
    summonUnits = assembleUnitsFromProtoData(summonUnitsProtoData)
    refreshUnits = assembleUnitsFromProtoData(refreshUnitsProtoData)
    kofUnits = assembleUnitsFromProtoData(kofUnitsProtoData)
    refreshBattleUnits = assembleUnitsFromProtoData(refreshBattleUnitsProtoData)
    serverRefreshUnits = assembleUnitsFromProtoData(serverRefreshUnitsProtoData)

    -- 创建逻辑entities
    local unitIds = self.mBattleLoop:createUnits()
    if unitIds then
        local myUid = UD.player.uid
        for i, unit in pairs(units) do
            local id = unitIds[i]
            if id then
                unit.id = id
            else
                printError("unit id error!!!!")
            end

            if unit.uid == myUid then
                myUnitId = id
            end

            if BattleManager:isSingleMode() then
                local dataindex = BattleManager:GetSingleIndex()
                if i == dataindex then
                    myUnitId = id
                end
            end

            --面朝右
            if unit.faceDir == Enums.Face.right and self.leftTeamId == nil then
                self.leftTeamId = unit.teamId
            end

            --面朝左
            if unit.faceDir == Enums.Face.left and self.rightTeamId == nil then
                self.rightTeamId = unit.teamId
            end
        end
    end

    self.myTeamId = units[myUnitId].teamId
    if self.myTeamId == self.leftTeamId then
        self.enemyTeamId = self.rightTeamId
    else
        self.enemyTeamId = self.leftTeamId
    end

    local commands = {}
    for i = 1, #cmds do
        local serverCommand = cmds[i]
        local command = self:createCommandFromServerCommand(serverCommand)
        self.mBattleLoop:enqueue(command)
    end

    local unit
    local entityModel
    --创建本地entities数据
    self.entities = {}
    for i = 1, #units do
        unit = units[i]
        entityModel = self:createEntityModel(unit.id, unit.protoUnit, myUnitId, false)
        self.entities[entityModel:getUnitModelId()] = entityModel
    end

    self.summonEntities = {}
    for i = 1, #summonUnits do
        unit = summonUnits[i]
        if unit.kind == Enums.UnitType.Npc then
            entityModel = self:createEntityModel(nil, unit.protoUnit, myUnitId, true)
            self.entities[entityModel:getUnitModelId()] = entityModel
            self.summonEntities[entityModel:getNpcId()] = entityModel
        else
            printError("summon unit kind Error: must be npc!!!!!")
        end
    end

    for i = 1, #refreshUnits do
        unit = refreshUnits[i]
        if unit.kind == Enums.UnitType.Npc then
            entityModel = self:createEntityModel(nil, unit.protoUnit, myUnitId, false, true)
            self.entities[entityModel:getUnitModelId()] = entityModel
        else
            printError("nextRefresh unit kind Error: must be npc!!!!!")
        end
    end

    for i = 1, #refreshBattleUnits do
        unit = refreshBattleUnits[i]
        if unit.kind == Enums.UnitType.Npc then
            local model = self:createEntityModel(nil, unit.protoUnit, myUnitId, false, true)
            self.entities[model:getUnitModelId()] = model
        else
            printError("refreshBattleUnits unit kind Error: must be npc!!!!!")
        end
    end

    for i = 1, #serverRefreshUnits do
        unit = serverRefreshUnits[i]
        if unit.kind == Enums.UnitType.Npc then
            local model = self:createEntityModel(nil, unit.protoUnit, myUnitId, false, true)
            self.entities[model:getUnitModelId()] = model
        else
            printError("serverRefresh player kind Error: must be npc!!!!!")
        end
    end

    if self:isKofBattle() then
        self.kofEntities = {}
        for i = 1, #kofUnits do
            unit = kofUnits[i]
            if unit.kind == Enums.UnitType.Player then
                local model = self:createEntityModel(nil, unit.protoUnit, myUnitId, false, false, true)
                self.entities[model:getUnitModelId()] = model
                self.kofEntities[i] = model
            else
                printError("kof player kind Error: must be player!!!!!")
            end
        end
    end

    self:prepare()

    self:dispatchEvent(BattleModel.Event_Start, {locationCount = self.battleEnterParam.mapX})
end

function BattleModel:enterBattle()
    print("BattleModel:enterBattle()")
    self.startTimeStamp = app:getUTCTime()
    local actualTime = self.battleEnterParam.actualTime
    self.mBattleLoop:start(app:getUTCTime(), actualTime)

    --和服务器同步是否是自动战斗
    self:dispatchEvent(BattleModel.Event_FirstSend_AutoBattle, {isAuto = UD.setting:getIsAutoBattle()})

    scheduler.newCron(function()
        if cc.isAlive(self) then
            self:dispatchEvent(BattleModel.Event_FirstSend_KeepBasicAttack, {isKeep = UD.setting:isKeepBasicAttack()})
        end
    end, 0.05)

    self:dispatchEvent(BattleModel.Event_Enter_Battle)

    self:go()

    --关闭竞技场界面
    if self:isPvpBattle() or self:isMeleeBattle() then
        UIHandler:handleMessage({id = Constant.FUNC_TYPE_ARENA_VS,mode = "close"})
    end 
end


function BattleModel:getBattleId()
    return self._battleId
end

function BattleModel:getLeftTeamId()
    return self.leftTeamId
end

function BattleModel:getRightTeamId()
    return self.rightTeamId
end

function BattleModel:prepare()
    --print("===========BattleModel:prepare============")
    for k, entityModel in pairs(self.entities) do
        if entityModel:isNpc() then
            local res = GD:queryNpcRes(entityModel:getNpcId())
            if res then
                entityModel:setHeight(tonumber(res.altitude) * 0.01)
                entityModel:setSize(tonumber(res.size))
            end
        elseif entityModel:isHero() then
            entityModel:setHeight(0)
            entityModel:setSize(500)
        end
    end

    local entities = {}
    for _,entityModel in pairs(self.entities) do
        table.insert(entities, entityModel)
    end

    table.sort(entities,function(l,r)
        local lsize = l:getSize()
        local rsize = r:getSize()
        return lsize > rsize
    end)

    for i, entityModel in ipairs(entities) do
        if entityModel:isHero() then
            if self.controlledEntityId == entityModel.id then
                --如果是自己
                entityModel:setDefaultZOrder( (i + 530)*10000 + entityModel:getSize())
            else
                entityModel:setDefaultZOrder((i + 30)*10000 + entityModel:getSize())
            end
        else
            entityModel:setDefaultZOrder(i*10000 + entityModel:getSize())
        end
    end
end

function BattleModel:isFastForward()
    if not cc.isDead(self.mBattleLoop) then
        return self.mBattleLoop:getFastForwardFlag()
    end
    return false
end

function BattleModel:isRewind()
    if not cc.isDead(self.mBattleLoop) then
        return self.mBattleLoop:getIsRewind()
    end
    return false
end

function BattleModel:getBattleType()
    return self.battleType
end
function BattleModel:isPvpBattle()
    return self.battleType == Enums.EBattleType.BtType_ArenaPvp
            or self.battleType == Enums.EBattleType.BtType_Throne
            or self.battleType == Enums.EBattleType.BtType_WorldGuildWar
end

function BattleModel:isMeleeBattle()
    return self.battleType == Enums.EBattleType.BtType_Melee
end

function BattleModel:isKofBattle()
    return self.battleType == Enums.EBattleType.BtType_Kof
end

function BattleModel:isZombieBattle()
    return self.battleType == Enums.EBattleType.BtType_Zombie
end

-- 歌剧院的PVE
function BattleModel:isTheatrePve()
    return self.battleType == Enums.EBattleType.BtType_TheatrePve
end

--狮心号角
function BattleModel:isLionBattle()
    return self.battleType == Enums.EBattleType.BtType_ShiXinHaoJiao
end

function BattleModel:isGuildWarBattle()
    return self.battleType == Enums.EBattleType.BtType_GuildWar
end

function BattleModel:isWorldGuildWarBattle()
    return self.battleType == Enums.EBattleType.BtType_WorldGuildWar
end

function BattleModel:isTraining()
    return self.battleType == Enums.EBattleType.BtType_Training
end

function BattleModel:isDuelBattle()
    return self.battleType == Enums.EBattleType.BtType_Duel
end


function BattleModel:initBuffs()
    --todo
end

function BattleModel:S2C_Start(msg, rawData)
    if not self.didStarted then
        local writablePath
        if DEBUG_BATTLE > 0 then
            --打印日志
            writablePath = cc.FileUtils:getInstance():getWritablePath().."/"..UD.player.uid
            local isDirectoryCreated = false
            local endDirectory = writablePath.."/records"
            if not cc.FileUtils:getInstance():isDirectoryExist(endDirectory) then
                isDirectoryCreated = cc.FileUtils:getInstance():createDirectory(endDirectory)
            else
                isDirectoryCreated = true
            end
            battle.BattleLogger:getInstance():setLevel(31)
            if isDirectoryCreated then
                battle.BattleLogger:getInstance():setWriteablePath(endDirectory)
            end
            battle.BattleLogger:getInstance():start()
            battle.BattleLogger:getInstance():setOutputConsole(false)
        end

        local battleCore
        if BattleManager:isSingleMode() and BattleManager:getSingleStartDataType() == Enums.SingleStartDataType.Proto then
            battleCore = battle.BattleCreator:createBattleCoreBySingleStart(rawData, string.len(rawData))
        else
            battleCore = battle.BattleCreator:createBattleCore(rawData, string.len(rawData))
        end

        if writablePath then
            battleCore:setLogPath(writablePath)
        end

        self:start(msg, battleCore)
        self.didStarted = true
    end
end

function BattleModel:S2C_InputCommand(msg)
    if msg then
        local result = msg.result or 0
        local serverCommand = msg.cmd

        if not self.isStartEnd then
            table.insert(self.waitProcessServerCommand,{result = result, serverCommand = serverCommand})
            return
        end

        self:processServerCommand(result,serverCommand)

        --test code: delay processServerCommand 触发回退逻辑
        -- local actions = {}
        -- table.insert(actions, cc.DelayTime:create(1.0))
        -- table.insert(actions, cc.CallFunc:create(function()
        --     self:processServerCommand(result,serverCommand)
        -- end))
        -- self:runAction(cc.Sequence:create(actions))

    end
end
--战前准备完毕 通知服务器可以开始
function BattleModel:onStartEnd()
    -- print("BattleModel:onStartEnd()")
    self:C2S_Ready()
    --通知ui加载到达 100%
    EM:notify("onBattleLoadEnd")
    self.isStartEnd = true
    for k,v in pairs(self.waitProcessServerCommand) do
        self:processServerCommand(v.result,v.serverCommand)
    end
end

function BattleModel:S2C_ErrorMessage(msg)
    if msg then
        if msg.content == "fight_battle_error_11" then
            -- 客户端服务器不匹配，特殊处理
            self._clientServerNotMatchMsg = GD:getMessageStr(msg.content)
        elseif msg.content == "fight_battle_error_16" then
            -- 战斗在服务器那边已经结束，特殊处理
            self._battleNotExistMsg = GD:getMessageStr(msg.content)
        else
            local str = GD:getMessageStr(msg.content)
            display.pushToast(str)
        end
    end
end

function BattleModel:S2C_SyncState(msg)
    if msg then
        local unitSyncs = {}
        for i = 1, #msg.units do
            local unitSync = msg.units[i]
            table.insert(unitSyncs, {id = unitSync.id, hp = unitSync.hp,})
        end
        self.waitProcessServerState[msg.actualTime] = {time = msg.actualTime, units = unitSyncs}
    end
end

--加载完毕，告知服务器 准备好了
function BattleModel:C2S_Ready()
    -- print("BattleModel:C2S_Ready")
    if self.mBattleLoop and BattleLoop_IsSingleMode(self.mBattleLoop) then
        self:enterBattle()
        return
    end

    local connector = self.connector
    if connector then
        -- print("battle2.C2S_Ready")
        connector:send("battle2.C2S_Ready",{progress = 100})
    end
    if self.isServerEnterBattle then
        -- 如果已经进入战斗, 则开始战斗逻辑
        self:enterBattle()
    end
end

function BattleModel:S2C_Ready(msg)
--    print("======> BattleModel:S2C_Ready()")
end

function BattleModel:S2C_UpdateReadyState(msg)
    -- dump(msg.states, "msg.states", 6)
    if msg then
        --更新玩家 是否准备好的状态
        EM:notify("onUpdateArenaAllLoadPercent", msg.states)
    end
end

function BattleModel:S2C_Enter(msg)
    -- print("BattleModel:S2C_Enter")
    if not self.isServerEnterBattle then
        -- 如果还没有进入战斗
        self.isServerEnterBattle = true
        --开始战斗逻辑
        self:enterBattle()
    end
end

function BattleModel:C2S_SwitchAutoBattle(bAuto)
    if bAuto == self.lastAutoBattle then
        return
    else
        self.lastAutoBattle = bAuto
    end
    if self.connector then
        self.connector:send("battle2.C2S_SwitchAutoBattle",{enable=bAuto})
    end
end

function BattleModel:S2C_SwitchAutoBattle(msg)
--    print("==========S2C_SwitchAutoBattle:", msg.result)
    --TODO: Auto battle operation
end

--function BattleModel:S2C_GameOver(msg)
--    print("====>S2C_GameOver")
--    local ret = msg.ret
--    local victoryDir = 0
--    if ret=="left" then
--        victoryDir = -1
--    elseif ret=="right" then
--        victoryDir = 1
--    else
--        victoryDir = 0
--    end
--
--    local isWin = false
--    local controlledEntity = self:getControlledEntity()
--    if controlledEntity then
--        local dir = controlledEntity:getDir()
--        if victoryDir == dir then
--            isWin = true
--        end
--    end
--
--    if not self.isGameOver then
--        self.isGameOver = true
--        self:addEvent({action="gameover",time=self.nextTickTime+2,victoryDir=victoryDir,isWin = isWin})
--    end
--
--    if self.connector then
--        print("==========S2C_GameOver close connect!!!")
--        self.connector:off()
--        self.connector:close()
--    end
--end

function BattleModel:pauseRunning()
    if self.running then
        self:dispatchEvent(BattleModel.Event_Pause)
        self.running = false
    end
end

function BattleModel:C2S_InputCommand(clientTime, command)
    local isSend = false
    local connector = self.connector
    local isServerEnterBattle = self.isServerEnterBattle
    -- 服务器进入到战斗流程才能发出指令
    if connector and isServerEnterBattle then
        local cmd = {
            unitId = command.unitId,
            seq = command.seq,
            cmdId = command.cmdId,
            skillId = command.skillId,
            targetType = command.targetType, --1 目标类型为id,
            targetValue = command.targetValue,
            actualTime = command.actualTime,
            flagId = command.flagId,
            InstanceID = command.InstanceID,
            BuyItemID = command.BuyItemID,
            BuyNum = command.BuyNum,
            interactionType = command.interactionType,
            subSkillId = command.subSkillId,
        }
        connector:send("battle2.C2S_InputCommand",{clientTime = clientTime, cmd = cmd})
        isSend = true
    end

--    print("====>C2S_InputCommand")
    return isSend
end

function BattleModel:C2S_Verify(event)
    -- local states = event.states
    local count = event.count
    local wonTeam = event.wonTeam
    local function S2C_Verify(msg,err)
        -- print("======S2C_Verify=====")
        --暂时不需要判断self是否为空，因为回调回来的时候，可能战斗结束，此处已经释放掉了
        -- local isnull = tolua.isnull(self)
        -- if not isnull then
            if type(err)=="nil" then
                local result = msg.result or 0
                -- print("==========S2C_Verify:",result)
                if result ~= 1 then
                    release_print("==========S2C_Verify not sync!!!")
                    if DEBUG > 0 then
                        app:popMessage("fight_battle_error_12", nil, nil, Enums.ToastTag.battle)
                    end
                end
            end
        -- end

        if self.connector then
            print("==========S2C_Verify close connect!!!")
            self.connector:off()
            self.connector:close()
            self.connector = nil
        end
    end

    local connector = self.connector
    if connector then
        connector:send("battle2.C2S_Verify", {count=count, wonTeam = wonTeam}, S2C_Verify)
    end

    print("====>C2S_Verify")
end

function BattleModel:addEvent(event)
    self.events[#self.events+1] = event
end

function BattleModel:hasNextEvent()
    return self.eventIdx <= #self.events
end

function BattleModel:go()
    self.running = true
end

function BattleModel:isRunning()
    return self.running
end

function BattleModel:stop()
    self.running = false
end

function BattleModel:onBattleLoopEnd(event)
    -- todo
    self.running = false
    self:dispatchEvent(BattleModel.Event_GameOver, event)
end

function BattleModel:handleBuffEvent(v)
    --todo
end

function BattleModel:handleBuffEvents(buffEvents)
    for _,v in ipairs(buffEvents) do
        self:handleBuffEvent(v)
    end
end

local EventParCache = {}

function BattleModel:tick_(dt)
    if not self.running then
        return
    end

    if self.isGameOver then
        if self.mBattleLoop then
            BattleLoop_tick_(self.mBattleLoop, dt, app:getUTCTime())
        end
        return
    end

    local connector = self.connector
    if connector then
        if connector:isClosed() then
            return
        end
        connector:tick(dt)
    end

    local battleLoop = self.mBattleLoop
    if battleLoop then
        BattleLoop_tick_(battleLoop, dt, app:getUTCTime())

        local currentState = BattleLoop_getCurrentState(battleLoop)
        if currentState then
            local leftEscapeTime = State_getLeftEscapeTime(currentState)
            local currentTime = BattleLoop_getBattleLogicTime(battleLoop)
            EventParCache.isRealStart = State_isGameStarted(currentState)

            local targetProgress
            local controlledEntity = self:getControlledEntity()
            if controlledEntity then
                local teamId = controlledEntity.teamId
                targetProgress = State_getTargetProgress(currentState,teamId)
            end

            EventParCache.logicEntities = {}
            local entityCount = State_getEntityCount(currentState)
            if entityCount > 0 then
                local entity
                local entityId
                for i=0,entityCount-1 do
                    entity = State_getEntityByIdx(currentState,i)
                    entityId = Entity_getId(entity)
                    EventParCache.logicEntities[entityId] = entity
                end
            end

            EventParCache.accumulator = BattleLoop_getAccumulator(battleLoop)
            -- print("===========currentTime:",currentTime)
            -- print("===========self.currentBattleLogicTime:",self.currentBattleLogicTime)

            local isNeedFetchSkills = false
            local isRewind = BattleLoop_getIsRewind(battleLoop)
            -- print("=========isRewind:",isRewind)
            if isRewind then
                isNeedFetchSkills = true
            end

            local isNeedSync = false
            if not self.currentBattleLogicTime or self.currentBattleLogicTime ~= currentTime then
                isNeedSync = true
                isNeedFetchSkills = true
            end

            if isNeedFetchSkills then
                -- print("=========isNeedFetchSkills:",isNeedFetchSkills)
                self:dispatchEvent(BattleModel.Event_Fetch_Self_Skills)
            end

            if isNeedSync then
                -- print("===========last sync:",self.currentBattleLogicTime)
                EventParCache.time = currentTime
                EventParCache.leftEscapeTime = leftEscapeTime
                EventParCache.targetProgress = targetProgress
                self:dispatchEvent(BattleModel.Event_Sync_Entity_State, EventParCache)
                self.currentBattleLogicTime = currentTime
            end
            EventParCache.dt = dt
            self:dispatchEvent(BattleModel.Event_Sync_UI_State, EventParCache)
            self:dispatchEvent(BattleModel.Event_Sync_Entity_Pos, EventParCache)
            self:dispatchEvent(BattleModel.Event_DBM_ON_HP, EventParCache)
        end

        BattleLoop_processEvents(battleLoop)

        local waitProcessServerState = self.waitProcessServerState
        local stateAlreadySyncTimes = BattleLoop_syncState(battleLoop, waitProcessServerState)
        if stateAlreadySyncTimes then
            for i, time in ipairs(stateAlreadySyncTimes) do
                waitProcessServerState[time] = nil
            end
        end
    end
end

function BattleModel:getBattleLogicTime()
    if self.mBattleLoop then
        return self.mBattleLoop:getBattleLogicTime()
    end
    return 0
end

function BattleModel:getGameLength()
    if self.mBattleLoop then
        return self.mBattleLoop:getGameLength()
    end
    return 0
end

function BattleModel:getMaxReviveTimes(teamId)
    if self.mBattleLoop then
        return self.mBattleLoop:getMaxReviveTimes(teamId)
    end
    return 0
end

function BattleModel:getInitReviveTimes(teamId)
    if self.mBattleLoop then
        return self.mBattleLoop:getInitReviveTimes(teamId)
    end
    return 0
end

function BattleModel:getLeftReviveTimes(teamId)
    if self.mBattleLoop then
        return self.mBattleLoop:getLeftReviveTimes(teamId)
    end
    return 0
end

function BattleModel:getDeadTimes(teamId)
    if self.mBattleLoop then
        return self.mBattleLoop:getDeadTimes(teamId)
    end
    return 0
end

function BattleModel:getBattleTargetString()
    local controlledEntity = self:getControlledEntity()
    if controlledEntity then
        local teamId = controlledEntity.teamId
        if self.mBattleLoop then
            return self.mBattleLoop:getBattleTargetString(teamId)
        end
    end
    return nil
end

function BattleModel:isBattleTimeCountDown()
    if self.mBattleLoop then
        return self.mBattleLoop:isBattleTimeCountDown()
    end
    return false
end

function BattleModel:resetMySkills()
    self.mySkillList = nil
end

function BattleModel:mySkills()
    if not self.mySkillList then
        if self.mBattleLoop then
            self.mySkillList = self.mBattleLoop:getEntitySkills(self.controlledEntityId)
            return self.mySkillList
        end
    end

    if self.mySkillList then
        for skillId,v in pairs(self.mySkillList) do
            if self.mBattleLoop then
                self.mySkillList[skillId] = self.mBattleLoop:getEntitySkillById(self.controlledEntityId,skillId)
            end
        end
    end
    return self.mySkillList
end

function BattleModel:mySkill(skillId)
    if self.mBattleLoop then
        return self.mBattleLoop:getEntitySkillById(self.controlledEntityId,skillId)
    end
    return nil
end

function BattleModel:myNextBasicTargetId()
    if self.mBattleLoop then
        return self.mBattleLoop:getEntityNextBasicTargetId(self.controlledEntityId)
    end
    return 0
end

function BattleModel:targetSelectionNeeded(skillConfig)
    if self.mBattleLoop then
        return self.mBattleLoop:targetSelectionNeeded(skillConfig)
    end
    return nil
end

function BattleModel:targetSelectionNeededBySkillId(skillId)
    local skill = self:mySkill(skillId)
    if skill then
        local cfg = skill:cfg()
        return self:targetSelectionNeeded(cfg)
    end
    return nil
end

function BattleModel:targetSelectionNeededBySkillId(skillId)
    local skill = self:mySkill(skillId)
    if skill then
        local cfg = skill:cfg()
        return self:targetSelectionNeeded(cfg)
    end
    return nil
end

function BattleModel:tryPointAndSkill(selectedPoint, selectedSkill)
    if (not selectedPoint) and (not selectedSkill) then
        return false
    end

    local instruction
    local isBasicAttack = false
    if selectedSkill then
        local skill = self:mySkill(selectedSkill)
        if skill then
            isBasicAttack = skill:isBasicAttack()
            instruction = {skill = selectedSkill, target = selectedPoint}
        else
            printError("can not find skill id:"..selectedSkill)
        end
    end

    if instruction == nil then
        return false
    end

    local seq = self:onInputSkill(instruction, isBasicAttack)
    if seq ~= nil then
        table.insert(self.currentCommandSeqs, seq)
    end

    return true
end

function BattleModel:getItemSkill(instanceId)
    if self.mBattleLoop and instanceId ~= nil then
        return self.mBattleLoop:getItemSkillByInstanceId(self.controlledEntityId,instanceId)
    end
    return nil
end

function BattleModel:tryToApplySkill(selectedTarget, selectedSkill, instanceId, selectedSubSkill)
    if (not selectedTarget) and (not selectedSkill) then
        return false
    end

    local instruction
    local isBasicAttack = false
    if selectedSkill then
        local skill = self:mySkill(selectedSkill) or self:getItemSkill(instanceId)
        if skill then
            isBasicAttack = skill:isBasicAttack()
            local cfg = skill:cfg()
            if not self:targetSelectionNeeded(cfg) then
                --如果该技能不需要选择目标
                instruction = {skill = selectedSkill, instanceId = instanceId, subSkill = selectedSubSkill,}
            elseif selectedTarget then
                --如果需要选择目标，并且选择了目标
                instruction = {skill = selectedSkill, target = selectedTarget, instanceId = instanceId, subSkill = selectedSubSkill,}
            end
        else
            printError("can not find skill id:"..selectedSkill)
        end
    end

    if instruction == nil then
        return false
    end

    local seq = self:onInputSkill(instruction, isBasicAttack)
    if seq ~= nil then
        table.insert(self.currentCommandSeqs, seq)
    end

    return true
end

function BattleModel:onBattleEscape()
    if self:isMeleeBattle() then
        app.session:request("pb_melee.C2S_MeleeRunaway",{})
    elseif self:isTheatrePve() then
        -- 歌剧院，主动调用歌剧院的退出，同时发送自动战斗消息
        if self:isRunning() then
            self:C2S_SwitchAutoBattle(true)
        end
        app:sendMsg("TheatreModel", "reqEscape")
        BattleUIEvent:clear()
        app:dispatchEvent({name="FORCE_QUIT"})
    else
        self:onInput(nil, Enums.CmdID.Cmd_Escape, Enums.TargetType.TargetType_None, nil)
        if BattleManager:isSingleMode() then
            BattleManager:setEscapeInSingleMode(true)
            app.session:request("battle2.C2S_SingleBattleEscape",
                {
                    battleID = self._connectParams.battleID,
                },
                function(ret)
                    if ret.ok then
--                        display.pushToast(L("单机战斗逃跑成功"))
                    end
                end)
        end
        if self:isTraining() then
            app.session:request("pb_training.C2S_Training_Leave", {})
        end
    end
end

function BattleModel:onTrainingBattleEscape()
    self:onInput(nil, Enums.CmdID.Cmd_Escape, Enums.TargetType.TargetType_None, nil)
    if BattleManager:isSingleMode() then
        BattleManager:setEscapeInSingleMode(true)
        app.session:request("battle2.C2S_SingleBattleEscape",
            {
                battleID = self._connectParams.battleID,
            },
            function(ret)
                if ret.ok then
                    --display.pushToast(L("单机战斗逃跑成功"))
                end
            end)
    end
end

function BattleModel:onTeamMark(event)
    local targetId = event.targetId
    local flagId = event.flagId
    local data = {target = targetId}
    self:onInput(data, Enums.CmdID.Cmd_SetFlag, Enums.TargetType.TargetType_Id, flagId)
end

function BattleModel:onPlayerTalk(event)
    local data = {content = event.content,}
    self:onInput(data, Enums.CmdID.Cmd_Talk, Enums.TargetType.TargetType_Id, nil)
end

function BattleModel:interactWithUnit(event)
    local data = {target = event.targetId, interactionType = event.interactionType}
    self:onInput(data, Enums.CmdID.Cmd_RealtimeInteraction, Enums.TargetType.TargetType_Id, nil,event.BuyItemID,event.BuyNum)
end

function BattleModel:onBuyItem(event)
    self:onInput(nil, Enums.CmdID.Cmd_BuyItem, Enums.TargetType.TargetType_Id, nil,event.BuyItemID,event.BuyNum)
end

function BattleModel:onUseItem(event)
    local data = {content = event.content}
    self:onInput(data, Enums.CmdID.Cmd_UseItem, Enums.TargetType.TargetType_Id, nil)
end


function BattleModel:setSkillTarget(targetId)
--    print("=========setSkillTarget===:", targetId)
    local data = {target = targetId}
    self:onInput(data, Enums.CmdID.Cmd_SetSkillTarget, Enums.TargetType.TargetType_Id, nil)
end

--主动复活
function BattleModel:onBattleRevive()
    --print("======onBattleRevive====BattleModel=========")
    self:onInput(nil, Enums.CmdID.Cmd_Revive, Enums.TargetType.TargetType_None, nil)

end

function BattleModel:isInRangeVision(targetId)
    return self.mBattleLoop:isInRangeVision(self.controlledEntityId, targetId)
end

function BattleModel:setKeepBasicAttack(isKeep)
    local flagId = 0
    if isKeep then
        flagId = 1
    end
    self:onInput(nil, Enums.CmdID.Cmd_KeepAttack, Enums.TargetType.TargetType_None, flagId)
end

function BattleModel:forceLockTarget(isForceLocked)
    local flagId = 0
    if isForceLocked then
        flagId = 1
    end
    self:onInput(nil, Enums.CmdID.Cmd_ForceLockTarget, Enums.TargetType.TargetType_None, flagId)
end

function BattleModel:onPlayerMove(event)
--    print("=======BattleModel:onPlayerMove=====", event.moveFlag)
    local moveFlag = event.moveFlag
    local data = {target = moveFlag}
    local cmd = Enums.CmdID.Cmd_MoveStart
    if moveFlag == Enums.MoveFlag.stop then
        cmd = Enums.CmdID.Cmd_MoveStop
    end
    self:onInput(data, cmd, Enums.TargetType.TargetType_Id, nil)
end

function BattleModel:onPlayerRoll(event)
    if self.controlledEntityModel:getRollSkill() then
        local controlledUnit = self.mBattleLoop:getEntityById(self.controlledEntityId)
        local skillRoll = controlledUnit:skillRoll()
        if skillRoll then
            if skillRoll:cd() == 0 then
                self:tryToApplySkill(0, skillRoll:id())
            else
                BattleUIEvent:dispatchEvent(BattleUIEvent.TYPE.SKILL_ROLLCD_HINT)
                display.pushToast(L("tips_rollcooldown"))
            end
        end
    end
end

function BattleModel:onInputSkill(data, isBasicAttack)
    if isBasicAttack then
        local param
        if UD.setting:isKeepBasicAttack() then
            param = 1
        end
        return self:onInput(data, Enums.CmdID.Cmd_Skill, Enums.TargetType.TargetType_Id, param)
    else
        if data.instanceId then
            return self:onInput(data, Enums.CmdID.Cmd_UseItem, Enums.TargetType.TargetType_Id, nil)
        else
            return self:onInput(data, Enums.CmdID.Cmd_Skill, Enums.TargetType.TargetType_Id, nil)
        end
    end
end

function BattleModel:onInputFinishChantCharge(skillId)
    local data = {skill = skillId }
    return self:onInput(data, Enums.CmdID.Cmd_FinishChantCharge, Enums.TargetType.TargetType_Id, nil)
end

function BattleModel:onInput(data, cmdId, targetType, flagId, buyItemID, buyNum)
--    if self.mBattleLoop:getFastForwardFlag() then
--        return
--    end
    local seq = self.outgoingCommandSeq + 1
    local command = self:makeClientCommand(seq, data, cmdId, targetType, flagId, buyItemID, buyNum)
    if command then
        self.outgoingCommandSeq = seq
        self.outgoingCommands[self.outgoingCommandSeq] = command

        local isSend
        if self.mBattleLoop and BattleLoop_IsSingleMode(self.mBattleLoop) then
            --如果是本地
            command.ms = {}
            self:process(command)
        else
            --如果是服务器
            local clientTime = self.mBattleLoop:getBattleLogicTime()
            isSend = self:C2S_InputCommand(clientTime, command)
        end
        if isSend then
            return self.outgoingCommandSeq
        end
    end
end

--本地战斗使用，录像回放不使用这个
 function BattleModel:process(command)
     if self.mBattleLoop and BattleLoop_IsSingleMode(self.mBattleLoop) then
         if self.mBattleLoop then
             --print("====================process===================")
             command.actualTime = self.mBattleLoop:getBattleLogicTime()
             local inCommand = self:createCommandFromServerCommand(command)
             self.mBattleLoop:enqueue(inCommand)
         end
     end
 end

function BattleModel:createCommandFromServerCommand(serverCommand)
    -- print("-----actualTime:",serverCommand.actualTime)
    -- print("-----seq:",serverCommand.seq)
    --print("-----cmdId:",serverCommand.cmdId)
    -- print("-----unitId:",serverCommand.unitId)
    -- print("-----skillId:",serverCommand.skillId)
    -- print("-----targetType:",serverCommand.targetType)
    -- print("-----targetValue:",serverCommand.targetValue)
    --print("-----content:",serverCommand.content)
    local cmdId = protobuf.enum_id("battle2.CmdID",serverCommand.cmdId)
    --print("---enum_id--cmdId:",cmdId)
    local targetType = 0
    if serverCommand.targetType then
        targetType = protobuf.enum_id("battle2.TargetType",serverCommand.targetType)
    end
    local command = battle.Command:create(
        serverCommand.unitId,
        serverCommand.seq or 0,--todo 不是自己的command需不需要改为0
        cmdId,
        serverCommand.skillId or 0,
        targetType, --1 目标类型为id
        serverCommand.targetValue or 0,
        -- serverCommand.clientTime or 0,
        0,
        serverCommand.actualTime or 0,
        serverCommand.content or "",
        serverCommand.flagId or 0,
        serverCommand.InstanceID or 0,
        serverCommand.BuyItemID or 0,
        serverCommand.BuyNum or 0,
        serverCommand.interactionType or 0,
        #(serverCommand.ms) or 0,
        serverCommand.subSkillId or 0
    )

    local monsterInfo
    for i = 1, #(serverCommand.ms) do
        monsterInfo = serverCommand.ms[i]
        command:setMonster(i-1, monsterInfo.monid, monsterInfo.pos)
    end

    return command
end

--  // process server command
function BattleModel:processServerCommand(result,serverCommand)
        -- print("--------processServerCommand----------")
        -- print("-----result:",result)
        -- print("-----actualTime:",serverCommand.actualTime)
        -- print("-----seq:",serverCommand.seq)
        -- print("-----cmdId:",serverCommand.cmdId)
        -- print("-----unitId:",serverCommand.unitId)
        -- print("-----skillId:",serverCommand.skillId)
        -- print("-----targetType:",serverCommand.targetType)
        -- print("-----targetValue:",serverCommand.targetValue)
        --todo
        local isSelfCommand = self:ourOwnCommand(serverCommand)
        if result == Enums.Result.RESULT_OK then --// maybe our command, maybe other's command
            if self.mBattleLoop then
                -- local command = nil
                -- if self:ourOwnCommand(serverCommand) then
                --     command = self:currentCommand()
                -- else
                --     --serverCommand 转换成 command
                --     command = self:createCommandFromServerCommand(serverCommand)
                -- end
                -- if command then
                --     self.mBattleLoop:enqueue(command) --// for further local processing
                -- end
                --print("====================processServerCommand===================")
                local command = self:createCommandFromServerCommand(serverCommand)
                if serverCommand and serverCommand.actualTime and serverCommand.actualTime > 0 then
                    if serverCommand.cmdId ~= -1 then
						if self.lastCommandTime > serverCommand.actualTime then
							printError("Error:serverCommand actualTime in past time!!!!!")
						else
							self.lastCommandTime = serverCommand.actualTime
						end
					end
                end
                self.mBattleLoop:enqueue(command) --// for further local processing
            end
        else
            if isSelfCommand then
                local cmdId = protobuf.enum_id("battle2.CmdID",serverCommand.cmdId)
                if cmdId == Enums.CmdID.Cmd_Skill then
                    --暂时 隐藏技能释放失败的提示
                    -- app:popMessage("fight_battle_error_08", nil, nil, Enums.ToastTag.battle)
                end
                if result == Enums.Result.RESULT_ERR_CODE then
                    app:popMessage("fight_battle_nettimeout_20", nil, nil, Enums.ToastTag.battle)
                end
            end
        end

        if isSelfCommand then
            -- print("id:",self.controlledEntityId,",failed command :",serverCommand.cmdId)
            --severCommand 装换成 command
            -- local command = self:currentCommand()
            -- self:postProcessed({time = serverCommand.actualTime, command = command, result = result})
            --todo
            self:clearPending(result,serverCommand)
        elseif result ~= Enums.Result.RESULT_OK then
            printError("BUG: should not received other's failed command")
        end
    -- end
end

function BattleModel:ourOwnCommand(serverCommand)
    return serverCommand.unitId == self.controlledEntityId
end

function BattleModel:makeClientCommand(seq, inputData, _cmdId, _targetType, flagId, buyItemID, buyNum)
    if self.mBattleLoop then
        local unitId = self.controlledEntityId
        local clientTime = self.mBattleLoop:getBattleLogicTime()
        local cmdId = protobuf.enum_name("battle2.CmdID",_cmdId) --2 skill
        local targetType = protobuf.enum_name("battle2.TargetType",_targetType)--1 目标类型为id,
        local skillId = inputData and inputData.skill or 0
        local subSkillId = inputData and inputData.subSkill or 0
        local targetValue = inputData and inputData.target or 0
        local content = inputData and inputData.content or ""
        local InstanceID = inputData and inputData.instanceId or 0
        local interactionType = inputData and inputData.interactionType or 0
        local command = {
            unitId = unitId,
            seq = seq,
            cmdId = cmdId,
            skillId = skillId,
            subSkillId = subSkillId,
            targetType = targetType, --1 目标类型为id,
            targetValue = targetValue,
            content = content,
            clientTime = clientTime,
            actualTime = 0,
            result = 0,
            flagId = flagId or 0,
            InstanceID = InstanceID,
            BuyItemID = buyItemID or 0,
            BuyNum = buyNum,
            interactionType = interactionType
        }
        return command
    end
    return nil
end

function BattleModel:postProcessed(event)
    local command = event.command
    local result = event.result or 0
    if command and (command:getUnitId() ~= self.controlledEntityId) then --只处理关于自己的
        return
    end

    local seq = command:getSeq()
    local processedCommand = self.outgoingCommands[seq]
    if processedCommand then
        self:clearPending(result, processedCommand)
    end
end

function BattleModel:clearPending(result,command)
    -- print("id:",self.controlledEntityId,",clearPending command :",serverCommand.cmdId)

    --todo
    local processedCommand = self.outgoingCommands[command.seq]
    if processedCommand then
        -- processedCommand:setResult(result)
        processedCommand.result = result
    end

--    print("------clearPending seq:",command.seq)
    local leftSeqs = {}
    for k,seq in pairs(self.currentCommandSeqs) do
        if command.seq ~= seq then
            table.insert(leftSeqs, seq)
        end
    end
    self.currentCommandSeqs = leftSeqs
    -- dump(self.currentCommandSeqs,"self.currentCommandSeqs",6)
end

function BattleModel:isPending(skillId)
    -- dump(self.currentCommandSeqs,"isPending self.currentCommandSeqs",6)
    for k,seq in pairs(self.currentCommandSeqs) do
        -- print("isPending seq:",seq)
        local command = self.outgoingCommands[seq]
        if command then
            if command.skillId == skillId  then
                return true
            end
        end
    end
    return false
end

--todo

-- function BattleModel:currentCommand(command)
--     if self.currentCommandSeq == nil then
--         return nil
--     end
--     return self.outgoingCommands[self.currentCommandSeq]
-- end

-- function BattleModel:resetCurrentCommand()
--     self.currentCommandSeq = nil
-- end

-- function BattleModel:currentSkillId()
--     local command = self:currentCommand()
--     if not command then
--         return 0
--     end

--     return command:getSkillId()
-- end

function BattleModel:alive()
    if self.mBattleLoop then
        return self.mBattleLoop:alive(self.controlledEntityId)
    end
    return false
end

-------------------------------------------------------------------
function BattleModel:getEntityModelById(uid)
    return self.entities[uid]
end

function BattleModel:getSummonEntityModelByNpcId(npcId)
    return self.summonEntities[npcId]
end

function BattleModel:getKofEntityModelByIndex(index)
    return self.kofEntities[index]
end

function BattleModel:getKofEntityModelByTeamIndex(teamId, index)
    local i = 0
    for k,v in pairs(self.kofEntities) do
        if v.teamId == teamId then
             i = i + 1
             if i == index then
                return v
             end
        end
    end
    return nil
end

function BattleModel:getControlledEntity()
    return self.controlledEntityModel
end

function BattleModel:isAboutMe(casterId, targetId)
    local controlledEntityId = self.controlledEntityId
    return casterId == controlledEntityId or targetId == controlledEntityId
end

-- 设置单人战斗暂停状态
function BattleModel:setBattlePause(b)
    if b then -- 暂停？
        self:pause()
    else -- 继续？
        self:resume()
    end
    if self.mBattleLoop then
        self.mBattleLoop:setBattlePause(b)
    end
end

return BattleModel
